package app.gui;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;

import app.Main;
import app.conn.WebDB;
import app.util.LI;

public class RecordsModel {
	public static final char ENV = 'E';
	public static final char ACT = 'A';	
	public static final int TRIGG_AUTO = 1;
	public static final int TRIGG_MAN = 0;
	public static final int ACT_VENT = 0;
	public static final int ACT_WAT = 1;
	public static final int STATE_ON = 1;
	public static final int STATE_OFF = 0;

	public static final long TIMESTEP = 3600;//seconds
	public static final int STEP_WIDTH = 100;//px


	private LinkedList<Record> temperature;
	private LinkedList<Record> humidity;
	private LinkedList<Record> moisture;

	private LinkedList<OnOff> ventilation;
	private LinkedList<OnOff> watering;

	private long minTime = Long.MAX_VALUE;
	private long maxTime;

	private boolean remoteAccess;
	private String unitId;

	/**
	 * Blank Constuctor
	 */
	public RecordsModel() {
		new RecordsModel(null);
	}

	/**
	 * Constructor
	 * 
	 * @param data Records data for parsing
	 */
	public RecordsModel(String[] data) {
		if(data != null)
			try {
				parseData(data);
			} catch (ParseException e) {
				Main.showError("Chyba dat", "Neplatný formát vstupních dat");
				return;
			}
	}


	/**
	 * Parse records data from string array
	 * - from file
	 * - from web
	 * 
	 * @param data Records data line by line
	 * @return True, is parsing successfully accomplished
	 * @throws ParseException Records in bad format
	 * @throws NumberFormatException Error in records data
	 */
	public void parseData(String[] data) throws ParseException, NumberFormatException {
		setTemperature(new LinkedList<Record>());
		setHumidity(new LinkedList<Record>());
		setMoisture(new LinkedList<Record>());
		setVentilation(new LinkedList<OnOff>());
		setWatering(new LinkedList<OnOff>());

		long time;
		SimpleDateFormat parser = new SimpleDateFormat("HHmmssyyyyMMdd");

		if(data != null)
			for(String line : data) {
				// parse time
				time = parser.parse(line.substring(1, 15)).getTime() / 1000;
				setMinTime(Math.min(time, getMinTime()));
				setMaxTime(Math.max(time, getMaxTime()));
				// parse record type
				switch(line.charAt(0)) {
				case ENV :	// parse environmental data
					int temp = Integer.parseInt(line.substring(15, 17));
					int hum = Integer.parseInt(line.substring(17, 20));
					int moist = Integer.parseInt(line.substring(20, 23));
					getTemperature().add(new Record(time, temp));
					getHumidity().add(new Record(time, hum));
					getMoisture().add(new Record(time, moist));
					break;

				case ACT :	// parse action data
					int trigg = Integer.parseInt(line.substring(15, 16));
					int act = Integer.parseInt(line.substring(16, 17));
					int state = Integer.parseInt(line.substring(17, 18));
					switch(act) {
					case ACT_VENT :	// ventilation states
						getVentilation().add(new OnOff(time, trigg, state));
						break;
					case ACT_WAT :	// watering states
						getWatering().add(new OnOff(time, trigg, state));
						break;
					}

					break;
				}
			}

		// sort all data by time
		Collections.sort(temperature);
		Collections.sort(humidity);
		Collections.sort(moisture);
		Collections.sort(ventilation);
		Collections.sort(watering);

		/*
		for(Record r : temperature) {
			System.out.println(r.toString());
		}
		for(Record r : humidity) {
			System.out.println(r.toString());
		}
		for(Record r : moisture) {
			System.out.println(r.toString());
		}
		for(OnOff r : ventilation) {
			System.out.println(r.toString());
		}
		for(OnOff r : watering) {
			System.out.println(r.toString());
		}
		*/
		// round min and max time for drawing correction
		roundBorderTimes();
	}

	/**
	 *  Round min-time to lower timestep and max-time to higher timestep
	 */
	private void roundBorderTimes() {
		setMinTime((getMinTime() / TIMESTEP) * TIMESTEP);
		setMaxTime(((getMaxTime() / TIMESTEP) + 1l) * TIMESTEP);
	}

	/**
	 * Clear records in storage media
	 */
	public void clear() {
		if(isRemoteAccess()) {
			try {
				WebDB.clearRecords(getUnitId());
			} catch (IOException e) {
				Main.showError("Chyba připojení", "Nelze odstranit záznamy z databáze");
			}
		}
		else {
			PrintWriter writer;
			try {
				writer = new PrintWriter(new FileWriter(MainFrame.getInstance().recFile));
				writer.print("");
				writer.close();
			} catch (IOException e) {
				Main.showError("Chyba souboru", "Nelze odstranit záznamy ze souboru");
			}
		}
	}
	
	/**
	 * Check that the model contains any data
	 * 
	 * @return True, if model contains no data
	 */
	public boolean isEmpty() {
		return temperature.isEmpty() && humidity.isEmpty() && moisture.isEmpty() &&
				ventilation.isEmpty() && watering.isEmpty();
	}

	/**
	 * Refresh Records Model
	 * - reload data from file or from DB
	 * 
	 */
	public void refresh() {
		if(isRemoteAccess()) {
			try {
				parseData(WebDB.getRecords(getUnitId()));
			} catch (NumberFormatException e) {
				Main.showError("Chyba souboru", "Chybný formát vstupních dat");			
			} catch (ParseException e) {
				Main.showError("Chyba souboru", "Chybný formát vstupních dat");
			} catch (IOException e) {
				Main.showError("Chyba připojení", "Nelze nahrát data z databáze");
			}
		}
		else {
			try {
				BufferedReader reader = new BufferedReader(new FileReader(MainFrame.getInstance().recFile));
				ArrayList<String> list = new ArrayList<String>();
				String line;
				while((line = reader.readLine()) != null) {
					list.add(line);
				}
				reader.close();
				String[] arr = list.toArray(new String[list.size()]);
				parseData(arr);

			} catch (IOException e) {
				Main.showError("Chyba souboru", "Nelze nahrát data ze souboru RECORDS.TXT");
				return;
			} catch (ParseException e) {
				Main.showError("Chyba souboru", "Chybný formát vstupních dat");
				return;
			} catch (NumberFormatException e) {
				Main.showError("Chyba souboru", "Chybný formát vstupních dat");
				return;
			}
		}

	}
	
	/**
	 * Get x position in chart
	 * 
	 * @param time Time of record
	 * @return X position in chart
	 */
	public int getTotalX(long time) {
		return (int) LI.interpolate(getMinTime(), getMaxTime(), 
				0, getWidth(), 
				time);
	}
	
	public long getTotalTime(int x) {
		return LI.interpolate(0, getWidth(), 
				getMinTime(), getMaxTime(), 
				x);
	}

	/**
	 * Get Chart panel width
	 * 
	 * @return Chart width in pixels
	 */
	public int getWidth() {
		return (int)(((getMaxTime() - getMinTime()) / TIMESTEP) * STEP_WIDTH);
	}

	/**
	 * Get minimal time in seconds
	 * 
	 * @return Minimal time in seconds from 1.1.1970 0:0:0
	 */
	public long getMinTime() {
		return minTime;
	}

	public void setMinTime(long minTime) {
		this.minTime = minTime;
	}
	
	/**
	 * Get maximal time in seconds
	 * 
	 * @return Maximal time in seconds from 1.1.1970 0:0:0
	 */
	public long getMaxTime() {
		return maxTime;
	}

	public void setMaxTime(long maxTime) {
		this.maxTime = maxTime;
	}

	/**
	 * Get records source
	 * 
	 * @return 	True, if the source is WebDB, 
	 * 			False, if data are loaded from file
	 */
	public boolean isRemoteAccess() {
		return remoteAccess;
	}

	public void setRemoteAccess(boolean remoteAccess) {
		this.remoteAccess = remoteAccess;
	}

	/**
	 * If records are loaded from WebDB, store Remote Unit ID
	 * 
	 * @return Remote Unit ID
	 */
	public String getUnitId() {
		return unitId;
	}

	public void setUnitId(String unitId) {
		this.unitId = unitId;
	}

	public LinkedList<Record> getTemperature() {
		return temperature;
	}

	public void setTemperature(LinkedList<Record> temperature) {
		this.temperature = temperature;
	}

	public LinkedList<Record> getHumidity() {
		return humidity;
	}

	public void setHumidity(LinkedList<Record> humidity) {
		this.humidity = humidity;
	}

	public LinkedList<Record> getMoisture() {
		return moisture;
	}

	public void setMoisture(LinkedList<Record> moisture) {
		this.moisture = moisture;
	}

	public LinkedList<OnOff> getVentilation() {
		return ventilation;
	}

	public void setVentilation(LinkedList<OnOff> ventilation) {
		this.ventilation = ventilation;
	}

	public LinkedList<OnOff> getWatering() {
		return watering;
	}

	public void setWatering(LinkedList<OnOff> watering) {
		this.watering = watering;
	}

	/**
	 * On-Off state times storage class
	 * 
	 * @author Lukas Vavra
	 *
	 */
	public class OnOff implements Comparable<OnOff> {

		private long time;
		private int trigger; // Auto / Manual on
		private int state; // On - Off
		private Color color; // Stripe color

		public OnOff() {
			new OnOff(-1, -1, -1);
		}

		public OnOff(long time, int trigger, int state) {
			this.setTime(time);
			this.setTrigger(trigger);
			this.setState(state);
		}

		public long getTime() {
			return time;
		}
		public void setTime(long time) {
			this.time = time;
		}

		public int getTrigger() {
			return trigger;
		}
		public void setTrigger(int trigger) {
			this.trigger = trigger;
			switch(trigger) {
			case TRIGG_AUTO :
				setColor(Color.green);
				break;
			case TRIGG_MAN :
				setColor(Color.red);
				break;
			}
		}
		public int getState() {
			return state;
		}

		public void setState(int state) {
			this.state = state;
		}

		public Color getColor() {
			return color;
		}
		public void setColor(Color color) {
			this.color = color;
		}
		/**
		 * Compare with other OnOff state used by sorting
		 */
		@Override
		public int compareTo(OnOff o) {
			if(this.getTime() == o.getTime()) {
				return 0;
			}
			else if(this.getTime() > o.getTime()) {
				return 1;
			}
			else {
				return (-1);
			}
		}

		public String toString() {
			SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss ");
			
			return "A @ " + sdf.format(time * 1000) + " : " 
					+ (trigger == TRIGG_AUTO ? "AUTO, " : "MAN,")
					+ (state == STATE_ON ? "ON" : "OFF");
		}
	}

	/**
	 * Records storage class
	 * implements Comparable to perform sort list
	 * 
	 * @author Lukas Vavra
	 *
	 */
	public class Record implements Comparable<Record> {
		private long time;
		private int value;

		public Record() {
			new Record(0, 0);
		}

		public Record(long time, int value) {
			this.setTime(time);
			this.setValue(value);
		}

		public boolean before(long time) {
			return this.getTime() < time;
		}

		public boolean after(long time) {
			return this.getTime() > time;
		}

		public long getTime() {
			return time;
		}

		public void setTime(long time) {
			this.time = time;
		}

		public int getValue() {
			return value;
		}

		public void setValue(int value) {
			this.value = value;
		}

		/**
		 * Compare with other Record used by sorting
		 */
		@Override
		public int compareTo(Record o) {
			if(this.getTime() == o.getTime()) {
				return 0;
			}
			else if(this.getTime() > o.getTime()) {
				return 1;
			}
			else {
				return (-1);
			}
		}

		public String toString() {
			SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss ");
			
			return "E @ " + sdf.format(time * 1000) + " : " + Integer.toString(value);
		}
	}
}
